source("binary_perceptron.R")
source("oneVersusAll.R") # for the getIndividualBinaryLabels function
require(nnet)

###########################
# One Versus One PERCEPTRON
###########################

############WHAT THIS SCRIPT DOES##################
# get (|people| choose 2) binary labels, each label y^{ij} has |TrainingData| components, where 
# y^{ij}_k = 1 if individual class of TrainingData[k] is i, y^{ij}_k = -1 if individual class of TrainingData[k] is j. 

# For each label l, train a perceptron using relevant data points (i.e. feed in a subset of y_s of y with 1 or -1 values, 
# feed in a subset of projectedDataMatix with y_s rows)

# Evaluation: for each test point, have each classifier vote. The class receiving the most vote wins
###################################################


# Let P = |people|
# Params:
#   trainY: a BOOLEAN value of binary labels
# Returns:
#   A  \binom{P}{2} by |TrainingData| matrix. For each row vector y_{ij}, its j-th component 
#     y_{ij} = 1  if name class of TrainingData[k] is i
#            = -1 if name class of TrainingData[k] is j
#            = NA    otherwise
getOneVersusOneBinaryLabels <- function(trainX, trainY) {
  result = matrix(FALSE, 0, NCOL(trainX))
  
  # a |people| by NCOL(trainX) matrix storing the ova binary label vectors
  OVA_Labels = trainY
  
  P = length(people)
  
  for (i in 1:(P-1)){
    for (j in (i+1):P){
      newRow = rep(FALSE, NCOL(trainX))
      ovaLabel_i = OVA_Labels[i, ]
      ovaLabel_j = OVA_Labels[j, ]  
      #print(sprintf("ovaLabel_i: %s", ovaLabel_i))
      #print(sprintf("ovaLabel_j: %s", ovaLabel_j))
      for (k in 1:NCOL(trainX)){
        if (ovaLabel_i[k] == TRUE){
          newRow[k] = 1
        } else if (ovaLabel_j[k] == TRUE){
          newRow[k] = -1
        } else {
          # this training data point is neither person i nor person j
          newRow[k] = NA
        }
      }
      
      result = rbind(result, newRow)
    }
  }
  
  return (result)
}

# Let P = |people|
P = length(people)

# a \binom{P}{2} by |trainX| matrix
# NOTE: this variable depends on data from the startup script
# this variable is being used in both learn and predict


#recognitionRates = rep(0, 10)

# Params:
#   trainX: a (non-mean-centered) matrix whose column vectors are the feature vectors
#   trainY: a boolean matrix generated by getIndividualBinaryLabels
# Returns: a list L, where
#   L$mistakeList = the mistakeIndicesList
#   L$projectedTrainX = a matrix whose row vectors are the feature vectors in the eigenspace
#   L$kernelDeg= the degree of the polynomial kernel in the model
#   L$eigenVecs= the top k eigenvectors (row-stacked)
#   L$OVO_Labs = the one versus one binary labels (see function getOneVersusOneBinaryLabels)
#   L$trainingMean = the mean of the column vectors in trainX
learnOVO <- function(trainX, trainY, param){
  # mean-center the trainX matrix
  trMean <- rowMeans(trainX)
  trainX <- trainX - trMean
  # change the arguments if want to tune a different hyperparam
  result <- learnHelperFuncOVO(trainX, trainY, k=40, kernDegree=param)
  result$trainingMean <- trMean
  
  return(result)
}

# Params:
#   k = dimension of eigenspace
#   deg = degree of polynomial kernel

learnHelperFuncOVO <- function(trainX, trainY, k=10, kernDegree=3){
  P = NROW(trainY)
  
  print(sprintf("OVO: Learning a model with k = %d and degree of polynomial kernel = %d, on %d training points...", k, kernDegree, NCOL(trainY)))
  
  print(sprintf("OVO: Getting OVO binary labels...", k, kernDegree))
  OVO_Labels = getOneVersusOneBinaryLabels(trainX, trainY)
  
  e <- getEigenVect(trainX %*% t(trainX),k) #get our basis functions
  print(sprintf("OVO: Finished selecting the top %d eigenvectors", k, kernDegree))
  
  # debug
  relevantDataIndicesList = list()
  
  # each element L[[i]] will be a numeric vector that corresponds to the index of the 
  # mistake points (in the whole training data ground set) made by the i-th perceptron
  # classifier, for all i in {1, ..., \binom{P}{2}}
  mistakeIndicesList = list()
  
  # each row R_i will be a k-dimensional vector representing the projection of
  # the i-th feature vector to the eigenspace
  projectedDataMatrix = matrix(numeric(0), 0, k)
  print("OVO: Projecting the training set onto the eigenspace...")
  for (i in 1:NCOL(trainX)){
    projectedDataMatrix = rbind(projectedDataMatrix, project(trainX[,i], e))
  }
  #print("NROW of projectedDataMatrix")
  #print(NROW(projectedDataMatrix))
  #deg = kernDegree
  #intersect = c(1:1)
  print("OVO: Training Classifiers...")
  for (j in 1:choose(P,2)){
    # a vector I consisting of all indices j such that OVO_Labels[j,] has a truth-value (instead of being NA)
    # Indices are in ascending order
    relevantDataIndices = which(!is.na(OVO_Labels[j,]))
    
    # debug
    relevantDataIndicesList[[j]] = relevantDataIndices
    #print(sprintf("relevantDataIndices: %s", relevantDataIndices))
    
    # a subset of the original binary label
    relevantBinaryLabel = (OVO_Labels[j,])[relevantDataIndices]
    
    # a matrix whose rows is a subset of the rows of the original projectedDataMatrix
    relevantProjectedDataMatrix = projectedDataMatrix[relevantDataIndices,]
    
    # train a perceptron classifier with polynomial kernel of degree 3
    # and record the mistake index vector
    # DEBUG
    
    #print("here")
    perceptronMistakesVec = perceptron(relevantBinaryLabel, relevantProjectedDataMatrix, getPolyKernel(kernDegree))
    #print("here2")
    #print(sprintf("perceptronMistakesVec: %s", perceptronMistakesVec))
    
    mistakeIndicesList[[j]] = relevantDataIndices[perceptronMistakesVec]
  }
  print(sprintf("Trained %d Classifiers", choose(P,2)))
  print(sprintf("OVO: Learned a model with k = %d and degree of polynomial kernel = %d", k, kernDegree))
  return (list(mistakeList=mistakeIndicesList, projectedTrainX=projectedDataMatrix, kernelDeg = kernDegree, eigenVecs=e, OVO_Labs=OVO_Labels))
}


# Params:
#   testX: a single feature vector of the test point
# Returns: a single number (the predicted class index)
predictOVO <- function(testX, model){
  
  mistakeIndicesList = model$mistakeList
  projectedDataMatrix = model$projectedTrainX
  kernDegree = model$kernelDeg
  e = model$eigenVecs
  OVO_Labels = model$OVO_Labs
  trMean <- model$trainingMean
  
  # a vector of length P used to store the votes coming from each perceptron classifier
  ballot = rep(0, length(people))
  
  # the projection of the mean-centered feature vector of the test data point
  projectedTestDatum = project(testX - trMean, e)
  
  # start collecting votes
  for (i in 1:(P-1)) {
    for (j in (i+1):P) {
      # get the index of the corresponding classifer in the list of classifiers
      rowIndex = (P * (i-1) + j) - (i)*(i+1)/2
      #print(paste("rowIndex:", rowIndex))
      
      mistakeIndices = mistakeIndicesList[[rowIndex]]
      
      # a vector of the labels of all the mistake points
      classifierMistakeLabels = (OVO_Labels[rowIndex,])[mistakeIndices]
      
      #print("OVA:classifierMistakeLabels")
      #print(classifierMistakeLabels)
      
      # a matrix of the features of all the mistake points
      classifierMistakeFeatures = projectedDataMatrix[mistakeIndices,]
      
      # a vector whose dimension is equal to size of the current mistake set      
      # The j-th component of pre_kern is equal to y^j * \phi(x^j) \cdot \phi(x^t)
      # where y^t = classifierMistakeLabels[t], 
      #       x^t = the t-th row of classifierMistakeFeatures
      #       phi = the feature transformation function
      # the sum of all the components gives the classification score of this data point          
      if (length(mistakeIndices) > 1) {
        pre_kern = classifierMistakeLabels * apply(classifierMistakeFeatures, 1, getPolyKernel(kernDegree), v=projectedTestDatum)
      } else {
        pre_kern = classifierMistakeLabels * (getPolyKernel(kernDegree))(classifierMistakeFeatures, projectedTestDatum)
      }
      
      #print(paste("prekern:", pre_kern))
      # vote
      if (sum(pre_kern) > 0) {
        # this classifier thinks this face is from individual i
        ballot[i] = ballot[i] + 1
      } else {
        # this classifier thinks this face is from individual j
        ballot[j] = ballot[j] + 1
      }
    }
  }
  #print(ballot)
  # the classification of this test data point
  # (breaking ties at random)
  classifiedIndex = which.is.max(ballot)
  #for (j in 1:length(ballot)){
  #  print(paste("ballot[", j, "]", ballot[j]))
  #}
  #print(paste("classified index on this test point is", classifiedIndex))
  result <- rep(FALSE, length(people))
  result[classifiedIndex] <- TRUE
  return (result)
}

###############################
# IMPLEMENTATION Test
###############################



# model <- learnOVO(A[,1:450], getIndividualBinaryLabels(people, TrainingData[1:454]), 10)
# error = 0
# 
# 
# Y_test = getIndividualBinaryLabels(people, TestData[1:23])
# for(i in 1:dim(Y_test)[2]) {
#   #       print("type of predict(testx[,i]),model")
#   #       print(typeof(predict(testx[,i],model)))
#   #       print("type of testy[,i]")
#   #       print(typeof(testy[,i]))
#   x <- getVector(TestData[i])
#   if(sum((predictOVO(x,model) - Y_test[,i])^2) == 2) {
#     error <- error + 1
#   }
# }
# errorRate <- error/NCOL(Y_test)  
# #   # grab the name of the person in testDatum
# #   testPersonName = gsub(".*faces/(.*)/.*", "\\1", testDatum)
# #   
# #   # if correctly recognized the individual, add the recognition score
# #   if (people[classifiedIndex] == testPersonName){
# #     print(sprintf("Correctly classified Test image %s", testDatum))
# #     correct = correct + 1
# #   } else {
# #     print(sprintf("Misclassified %s as %s", testPersonName, people[classifiedIndex]))
# #     print(sprintf("Test image path: %s", testDatum))
# #   }  
# }
# print(paste("Recognition Rate =", correct/length(TestData)))

#people <- c("ben", "jerry", "cameron")
#TrainingData <- c("faces/cameron/f1", "faces/jerry/f1", "faces/ben/f1", "faces/cameron/f2")

#M = getOneVersusOneBinaryLabels()
